SpringMVC + MyBatis 实现仿知乎问答项目笔记

问答项目笔记

Day1

参数解析

1
2
3
@Controller
public class IndexController {
    @RequestMapping("/") 
@ResponseBody public String index() { return "Hello NowCoder";
} }
1
2
3
4
5
@RequestMapping(value = "/profile/{groupId}/{userId}") 
@ResponseBody public String profile(@PathVariable("groupId") String groupId,
@PathVariable("userId") int userId,
@RequestParam(value = "type", defaultValue = "1") int type,
@RequestParam(value = "key", defaultValue = "nowcoder") String key) { return String.format("{%s},{%d},{%d},{%s}", groupId, userId, type, key);
  1. @RequsetMapping和@ResponseBody
    @RequsetMapping(path = {“/index”, “/“}, method = {RequestMethod.GET})表示地址映射,/index和/ 都可以以GET请求方式访问
    @ResponseBody表示返回的是一个文本,而不是模板
  2. @PathVariable和@RequestParam
    @PathVariable用来解析获取路径中的参数
    @RequestParam用来解析获取请求参数
1
2
3
4
5
public String profile(@PathVariable("userId") int userId,
@PathVariable("groupId") String groupId,
@RequestParam(value = "type", defaultValue = "1") int type,
@RequestParam(value = "key", defaultValue = "zz", required = false) String key) {
return String.format("Profile Page of %s / %d, t:%d k:%s", groupId,userId,type,key);

velocity语法

$!{ 变量/表达式 }

注释 ## # 多行注释

1
2
3
#foreach ($color in $colors) 
Color$!{foreach.count}/${foreach.index}:$!{color}
#end

$!{变量/表达式}${变量/表达式}的区别
$!{}中数据为空时,强制不显示
${}中数据为空时,当做文本显示

属性访问

$!{user.name}
$!{user.getName()}

模板继承

include 纯文本扩展
parse 变量解析

macro宏

1
2
3
4
#macro (render_color, $color, $index) 
This is Color $index:$color #end
#foreach ($color in $colors)
#render_color($color, $foreach.index) #end

request/response

request

参数解析
cookie读取
http请求字段
文件上传

HttpServletRequest

1
2
3
4
request.getHeaderNames(); 
request.getMethod();
request.getPathInfo();
request.getQueryString();

response

页面内容返回
cookie下发
http字段设置,headers
HttpServletResponse

response.addCookie(new Cookie(key, value)); 
response.addHeader(key, value);
1
2
3
4
5

### Enumeration接口
Enumeration有两个方法:
(1)boolean hasMoreElements();//是否还有元素,如果有返回true,否则表示至少含有一个元素
(2)E nextElement();//如果Enumeration枚举对象还有元素,返回对象只能的下一个元素,否则抛出NoSuchElementException异常。
public class TestEnumeration{ public static void main(String[] args){ Vector v = new Vector(); v.addElement("Lisa"); v.addElement("Billy"); v.addElement("Mr Brown"); Enumeration e = v.elements();//返回Enumeration对象 while(e.hasMoreElements()){ String value = (String)e.nextElement();//调用nextElement方法获得元素 System.out.print(value); } } }
1
### Error
// Spring MVC外的Exception或Spring MVC没有处理的Exception @ExceptionHandler() @ResponseBody public String error(Exception e) { return "ERROR:" + e.getMessage(); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

### IOC(依赖注入)
`@Autowired`自动注入

### AOP(面向切面,所有业务都要处理的业务)
日志Log


## Day2
### 数据库字段设计
每一个实体就是数据库中的一张表,表与表之间是有关联的。

#### 用户
id, name, password, salt, head_url
>salt:用于密码加密

#### 站内信
id, fromid, toid, content, conversation_id, created_date

#### 问题
id, title, content, user_id, created_date, comment_count
>comment_count:冗余设计,提高性能,不需要查询另一张表,可以异步更新

#### 评论
id, content, user_id, created_date, entity_id, entity_type

### 数据库创建
- PK: primary key 主键
- NN: not null 非空
- UQ: unique 唯一
- BIN: binary column 存放二进制数据的列
- UN: Unsigned data type 无符号数据类型(需要整数形数据)
- ZF: Fill up values for that column with 0's if it is numeric 填充0位(例如指定3位小数,整数18就会变成18.000)
- AI: Auto Incremental 自增长

#### 常用数据类型
- int
- varchar(n)
- datetime
- float(m,d)
- text,65535


### CRUD操作
- 增
`INSERT INTO table_name (列1, 列2,...) VALUES (值1, 值2,....)`
- 删
`DELETE FROM 表名称 WHERE 列名称 = 值`
- 改 `UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值`
- 查
`SELECT */列名称 FROM 表名称`

>`SELECT {COLA,COLB,*} FROM {TABLE} WHERE {CONDITION} ORDER BY {COL} DESC LIMIT {OFF},{COUNT}`

### MyBatis
#### JDBC数据库操作
1. 加载驱动
2. 创建数据库链接
3. 创建Statement对象
4. 执行SQL获取数据
5. 数据转化
6. 资源释放

#### MyBatis集成
1. application.properties 增加spring 配置数据库链接地址
2. pom.xml 中引入 mybatis-spring-boot-starter 和 mysql-connector-java 依赖
3. 定义 DAO 接口,注解为 Mapper,然后就可以直接 @Autowired 注入就可以使用了

三层结构:

Dao层读取数据库 -> Service层调用Dao层 -> Controller层调用Service
### MyBatis配置方法
#### 注解方式
@Mapper public interface UserDAO { String TABLE_NAME = "user"; String INSERT_FIELDS = " name, password, salt, head_url "; String SELECT_FIELDS = " id, name, password, salt, head_url "; @Insert({"insert into ", TABLE_NAME, "(", INSERT_FIELDS, ") Values (#{name}, #{password}, #{salt}, #{headUrl})"}) int addUser(User user); @Select({"select ", SELECT_FIELDS, " from ", TABLE_NAME, " where id=#{id}"}) User selectById(int id); @Update({"update ", TABLE_NAME, " set password = #{password} where id=#{id}"}) void updatePassword(User user); @Delete({"delete from ", TABLE_NAME, " where id = #{id}"}) void deleteById(int id); }
1
#### XML方式
<mapper namespace="com.nowcoder.dao.QuestionDAO"> <sql id="table">question</sql> <sql id="selectFields">id,title, content,comment_count,created_date,user_id</sql> <select id="selectByUserIdAndOffset" resultType="com.nowcoder.model.Question"> SELECT <include refid="selectFields"/> FROM <include refid="table"/> <if test="userId != 0"> WHERE user_id = #{userId} </if> ORDER BY id DESC LIMIT #{offset},#{limit} </select> </mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
### ViewObject和DateTool
ViewObject:ViewObject:方便传递任何数据到Velocity
DateTool:velocity自带工具类,用于自定义时间格式


## Day3
### 注册
1. 用户名合法性检测(长度,敏感词,重复,特殊字符)
2. 密码长度要求 3. 密码salt加密,密码强度检测(md5库)
4. 用户邮件/短信激活

### 登陆/登出
#### 登陆:
1. 服务器密码校验/三方校验回调,token登记 1. 服务器端token关联userid 1. 客户端存储token(app存储本地,浏览器存储cookie)
2. 服务端/客户端token有效期设置(记住登陆) 注:token可以是sessionid,或者是cookie里的一个key #### 登出:
服务端/客户端token删除
session清理

### 页面访问
#### 客户端
带 token 的 HTTP 请求
#### 服务端
1. 根据 token 获取用户 id
2. 根据用户 id 获取用户的具体信息
3. 用户和页面访问权限处理
4. 渲染页面/跳转页面

### 拦截器
判断用户身份
public class PassportInterceptor implements HandlerInterceptor{ boolean preHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception; void postHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3, ModelAndView var4) throws Exception; void afterCompletion(HttpServletRequest var1, HttpServletResponse var2, Object var3, Exception var4) throws Exception; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#### preHandle
1. 找出 Cookie 中的 ticket
2. 调用 loginTicketDAO 从数据库中找到对应的 ticket,并检验是否过期,状态是否有效
3. 调用 userDAO 从数据库中找到对应的用户
4. 将用户信息存放入 hostHandle中

> hostHandle 是为了实现随处都可以通过依赖注入的方式访问需要的信息

#### postHandle
将 hostHandle 中的用户信息加入到 modelAndView 中,用于velocity 调用

#### afterCompletion
清理工作

#### 拦截器配置
通过继承 WebWvcConfigurerAdapter 类,并重写 addInterceptors 方法,将自定义的拦截器加入访问链路中。
![拦截器](media/14886163470022/%E6%8B%A6%E6%88%AA%E5%99%A8.png)
@Component public class WendaWebConfiguration extends WebMvcConfigurerAdapter{ @Autowired PassportInterceptor passportInterceptor; //重写方法,加入自定义的拦截器 @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(passportInterceptor); super.addInterceptors(registry); } }
1
2
3

### 未登录跳转
通过拦截器过滤实现
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginRequiredInterceptor).addPathPatterns("/user/*"); super.addInterceptors(registry); } //访问跳转 @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { if (hostHolder.getUser() == null) { httpServletResponse.sendRedirect("/reglogin?next=" + uri); return false; } return true; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
 

## Day4
### 问题发布
pom.xml 添加 com.alibaba 的 fastjson 依赖
发布成功返回 json 串中 code 为 0,失败为 1。
>注意使用 HtmlUtils.htmlEscape() 方法进行 html 标签处理,防止 cookies 泄露


### 敏感词过滤
将敏感词构造成前缀树,然后一次遍历 question.title 和 question.content 就可以了
#### 前缀树
- 根节点不包含字符,除根节点外每一个节点都只包含一个字符 - 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串
- 每个节点的所有子节点包含的字符都不相同
![前缀树](media/14886163470022/%E5%89%8D%E7%BC%80%E6%A0%91.png)


### 多线程
- 优势:
- 充分利用多处理器
- 可以异步处理任务
- 问题:
- 数据被多个线程访问,数据的安全性问题
- 不活跃的线程也会占用内存资源
- 死锁

#### 实现多线程
1. extends Thread,重载 run() 方法
2. implement Runnable(),实现 run() 方法


#### Synchronized 内置锁
1. 放在方法上会锁住所有synchronized方法
2. synchronized(obj) 锁住相关的代码段

#### BlockingQueue 同步队列
BlockingQueue 阻塞队列。该类是 java.util.concurrent 包下的重要类,通过对 Queue 的学习可以得知,这个 queue 是单向队列,可以在队列头添加元素和在队尾删除或取出元素。类似于一个管道,特别适用于先进先出策略的一些应用场景。


| | 抛出异常 | 特殊值 | 阻塞 | 超时 |
| :-: | :-: | :-: | :-: | :-: |
| 插入 | add(e)| offer(e) | put(e) | offer(e, time, unit) |
| 移除 | remove() | poll() | take() | poll(time, unit) |
| 检查 | element() | peek() | 不可用 | 不可用 |

BlockingQueue 方法以四种形式出现,对于不能立即满足但可能在将来某一时刻可以满足的操作,这四种形式的处理方式不同:

- 第一种是抛出一个异常
- 第二种是返回一个特殊值(null 或 false,具体取决于操作)
- 第三种是在操作可以成功前,无限期地阻塞当前线程
- 第四种是在放弃前只在给定的最大时间限制内阻塞。


#### ThreadLocal
1. 线程局部变量。即使是一个 static 成员,每个线程访 问的变量是不同的。 2. 常见于web中存储当前用户到一个静态工具类中,在线程的任何地方都可以访问到当前线程的用户。 3. 参考 HostHolder.java 里的 users

>实现:每个 Thread 都持有一个 TreadLocalMap 类型的变量(该类是一个轻量级的 Map,功能与 map 一样,区别是桶里放的是 entry 而不是 entry 的链表。功能还是一个 map。)以本身为 key,以目标为 value。
主要方法是 get() 和 set(T a) ,set 之后在 map 里维护一个 threadLocal -> a,get 时将 a 返回。ThreadLocal 是一个特殊的容器。

#### Executor
1. 提供一个运行任务的框架。 2. 将任务和如何运行任务解耦。 3. 常用于提供线程池或定时任务服务
ExecutorService service = Executors.newFixedThreadPool(2); service.submit(new Runnable() { @Override public void run() { for (int i = 0; i < 10; ++i) { try { Thread.sleep(1000); System.out.println("Executor1:" + i); } catch (Exception e) { e.printStackTrace(); } } } });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259

#### Future

## Day6
### 通用的新模块开发流程

1. Database Column(字段设计) 2. Model:模型定义,和数据库相匹配 3. DAO:数据读取 4. Service:服务包装 5. Controller:业务入口
6. Test


### 评论中心
统一的评论服务,覆盖所有的实体评论
字段设计:

- id - content
- entity_id ->questionId/commentId
- entity_type question/comment
- created_date
- user_id


### 消息中心
字段设计:

- id
- from_id
- to_id
- content
- created_date
- has_read
- conversation_id

## Day7
### Redis
#### Redis简介
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

#### Redis安装
[Redis安装配置](http://www.jianshu.com/p/6b5eca8d908b)
[Redis快速教程](http://www.yiibai.com/redis/redis_quick_guide.html)
mac下Redis安装
1. 下载最新的稳定版Redis安装包 redis*.tar.gz
2. 在/usr/local目录下解压 `tar - zvxf redis*.tar.gz`
3. 进入/usr/local/redis目录下,输入`sudo make test`,等待完成
4. 输入`sudo make install`,完成安装
5. 启动Redis,输入命令`redis-server`

#### Redis数据结构
##### List
**双向列表,适用于最新列表,关注列表**

- lpush
- lpop
- blpop
- lindex
- lrange
- lrem
- linsert
- lset
- rpush

##### Set
**适用于无顺序的集合,如点赞点踩、抽奖、已读、共同好友**

- sdiff
- smembers
- sinter
- scard

##### SortedSet
**排行榜,优先队列**

- zadd
- zscore
- zrange
- zcount
- zrank
- zrevrank

##### Hash
**对象属性不定长属性数**

- hset
- hget
- hgetAll
- hexists
- hkeys
- hvals

##### KV
**单一数值,验证码、PV、缓存**

- set
- setex
- incr

### Redis实现踩赞功能
底层通过一个@Service的概念,将每个元素的赞跟踩存到Redis的集合里,Key一定要独立,通过RedisKeyUtil将entityType和entityId生成独立的Key,点赞则将userId放入集合

## Day8
### 异步队列
Redis异步队列实现点赞站内信发送

1. EventProducer的fireEvent将消息lpush到队列(RedisList)中。
2. 各种Handler实现EventHandler接口,其中有两个方法,事件处理方法和获取支持的事件类型。
3. EventConsumer通过applicationContext.getBeansOfType(EventHandler.class)自动找到所有实现EventHandler的Handler类,并存储在Map1<String, EventHandler>中。
4. 迭代访问Map1,获取每个Handler支持的所有事件类型,存储在List中,并迭代访问List,并将其Handler注册在Map<EventType, List<EventHandler>> config 中。
5. 消费者线程取出event后,查询config,获取所有Handler并执行


**什么是消息队列?**
所谓消息队列,就是一个以队列数据结构为基础的一个实体,这个实体是真实存在的,比如程序中的数组,数据库中的表,或者redis等等,都可以。

**首先我们说说为什么要使用队列,什么情况下才会使用队列?**
我的理解是,那些实时性要求不高,且比较耗时的任务,是队列的最佳应用场景。比如说我在某网站注册一个账号,当我的信息入库注册成功后,网站需要发送一封激活邮件,让我激活账号,而这个发邮件的操作并不是需要实时响应的,没有必要卡在那个注册界面,等待邮件发送成功,再说发送邮件本来就是一个耗时的操作(需要调用第三方smtp服务器),此时,选择消息队列去处理。注册完成,我只要向队列投递一个消息,消息的内容中包含我要发送邮件的一些设置,以及发送时间,重试次数等消息属性。这里的投递操作(可以是入库,写入缓存等)是要消息进入一个实体的队列。其中应该有一进程(消费者)一直在后台运行,他不断的去轮训队列中的消息(按照时间正序,队列是先进先出),看有没有达到执行条件的,如果有就取出一条,根据消息配置,执行任务,如果成功,则销毁这条消息,继续轮训,如果失败,则重试,知道达到重试次数。这时用户已经收到注册成功的提示,但是已经去做其他事了,邮件也来了,用户点击邮件,注册成功。这就是消息队列的一个典型应用。
再说一个场景,点赞,这个在高并发的情况下,很容易造成数据库连接数占满,到时整个网站响应缓慢,才是就是想到要解决数据库的压力问题,一般就是两种方案,一是提高数据库本身的能力(如增加连接数,读写分离等),但是数据库总是有极限的,到达了极限是没有办法在提升了的,此时就要考虑第二种方案,释放数据库的压力,将压力转移到缓存里面。就拿实际的点赞来说吧,用户的点赞请求到来,我只是将点赞请求投递到消息队列里面,后续的点赞请求可以将消息合并,即只更新点赞数,不产生新的任务,此时有个进行再不断的轮训消息队列,将点赞消息消耗,并将值更新到数据库里面,这样就有效的降低了数据库的压力,因为在缓存层将数个数据库更新请求合并成一个,大大提高了效率,降低了负载。

### 邮件发送


## Day9
### 关注服务
#### 概念
1. A 关注 B
2. A 是 B 的粉丝
3. B 是 A 的关注者

#### 特点
1. 多对多的服务
2. ID 和 ID 的关联,有序

#### Service
1. 通用关注接口
2. 粉丝列表分页
3. 关注对象列表分页

#### Controller
1. 首页问题关注数
2. 详情页问题关注列表
3. 粉丝/关注人列表
4. 关注异步事件

#### 存储结构
Redis: zset/list

### Redis事务
Redis 在接受到一个 client 发来的命令后会立即处理并返回处理结果,但是当一个 client 在一个连接中发出 multi 命令时,这个连接会进入一个事务上下文,该连接后续的命令并不是立即执行,而是先放到一 个队列中。当从此连接受到 exec 命令后,redis 会顺序的执行队列中的所有命令。并将所有命令的运行结果打包到一起返回给 client.然后此连接就结束事务上下文。

### 排序算法
#### Hacker News
[Hacker News](https://news.ycombinator.com/)
`Score=(P-1)/(T+2)^G`

- P:投票数,-1是把自己投的过滤掉
- T:发布到现在的时间间隔,单位小时,+2防止除数太小
- G:重力加速度,分值根据时间降低速率

#### Reddit
[Reddit](https://www.reddit.com/)
$ f(t_{s}, y, z)=log_{10}z +\frac{ yt_{s}}{45000}N$



>时间是最重要的权重,由于流量比较大,所以对于高赞文章有所优势,适合新闻类排序


#### StackOverflow
[StackOverflow](http://stackoverflow.com)
![StackOverflo](media/14886163470022/StackOverflow.png)

- Qviews:问题浏览数,通过log来平滑
- Qanswer:问题回答数,有回答的题目才是好问题
- Qscore:问题赞踩差,赞的越多,问题越好
- sum(Ascores):回答赞踩差,回答的越多问题越好
- QageInHours:题目发布时间差,时间越久排名越后
- Qupdated:最新的回答时间,越新关注度越高

#### IMDB
[IMDB](http://www.imdb.com/help/show_leaf?votestopfaq)
加权排名 (WR) = (v ÷ (v+m)) × R + (m ÷ (v+m)) × C
- R = 某电影投票平均分 - v = **有效**投票人数 - m = 最低投票人数,1250
- C = 所有电影平均值
>投票人数越多,越偏向于用户打分值,防止冷门电影小数人高分导致的高分


#### Topcoder
https://community.topcoder.com/longcontest/?module=Static&d1=support&d2=ratings

用于计算编程比赛排名

## Day10
### timeline/feed流过程
1. 事件触发产生新鲜事
2. 粉丝新鲜事列表获取
3. 各新鲜事自定义渲染
4. 新鲜事排序显示
5. 广告推荐整合


### 推拉模式结合

| | 定义| 用处 | 优缺点 |
| :---: | :--- | :---| :--- |
| 推 | 事件触发后广播给所有<br>的粉丝 | 写时消息推送给粉丝。<br>空间换时间 | 对于粉丝数过多的事件<br>后台压力较大,浪费存<br>储空间。流程清晰,开<br>发难度低,关注新用户<br>需要同步更新feed流 |
| 拉| 登陆打开页面的时候根<br>据关注的实体动态生成<br>timeline内容 |读时拉取所有好友的消<br>息,再聚合。时间换空间 | 读取压力大,存储占用<br>小,缓存最近读取的feed,<br>根据时间分区拉取 |
| 推拉结合 | 活跃/在线用户推,<br>其他用户拉 | 基于推,混入拉;基于<br>拉,加速推。时空平衡 | 降低存储空间,又满足<br>大部分用户的读取需求 |

### 新鲜事存储/渲染
![](media/14886163470022/14894917198966.jpg)

获取新鲜事到显示的过程
1. timeline新鲜事统一存储
2. 用户保存新鲜事id列表
3. 根据新鲜事id找到核心数据
4. 根据核心数据获取新鲜事类型
5. 根据类型选取对应的新鲜事模板进行渲染

### 微博架构演化
**第一版** 1. 纯推送,推送延迟,压力大,数据存储 浪费 **第二版** 1. 推送给有效用户(当天登陆过的用户)
2. 大号拉,小号推,整合feed流 3. 按月时间拆分保存,索引每月微博边界
4. 异步处理,发送成功先在自己流里可见 **第三版** 1. 服务化,feed按月,私信按uid拆分
2. 分级缓存-热门,短期,历史微博 3. 多机房数据同步

### 人人网架构
1. 以推送为主,新鲜事合并
2. 内存缓存关系链 3. 数据压缩缓存quicklz 4. 异步线程池 5. flyweight

### 新鲜事开发和优化
#### 开发 1. EventHandler 2. FeedDAO/Service/Model
3. Redis队列存储

#### 优化 1. 多好友合并去重 2. 关联实体删除清理 3. 取消/新增关注实时更新
4. 分时段存储 5. 缓存和增量拉

#### 拉模式实现过程
所有的feed都会通过一个feedHandler的事件监听器,当Handler监听到事件以后,就会把事件存储在数据库feed表中;当需要显示数据时,则找出所有我关注的人,再找出关注的人现在发生的事件,统一取出然后渲染页面展示
>拉的模式就是触发了,然后将事件存储下来,等到想看的时候,则去拉我关注的人的最近产生的所有的feed,然后排序显示。
>适用于僵尸用户,关注者多的大号

#### 推模式实现过程
所有的feed都会通过一个feedHandler的事件监听器,当Handler监听到事件以后,就会把事件lpush给这个事件的每一个关注者;关注者上线后取得最近发生的事件的id,然后取出对应的事件,渲染展示即可
>推的模式就是触发以后就在所有关注此问题或者用户的人的feed流里都加一个刚触发的事件。
>适合用于推给有效用户、活跃用户、关注者少的小号

## Day11
### 爬虫
- 手动:requests,beautifulsoup,urllib2,httpclient
- 入门:pyspider,webmagic
- 专业:scrapy,nutch
-
### pyspider

### Processor/CSS选择器

| 选择器 | 描述 |
| :-- | :-- |
|.class|class=“class”| |#id| `<p id=“id”>` | |div.inner|`<div class=“inner”>`| |`a[href^=“http://”]`|带http开头href的a元素| |p div|p元素下的div元素(不必父子)| |p>div>span|p元素下的div元素下的span| |[target=_blank]|Target=_blank|

`div.inner>a` 意思是 `<div class="inner">`元素下的a元素
<div class="inner"> <a href="https://www.v2ex.com/go/retroarch" class="item_node">RetroArch</a><a href="https://www.v2ex.com/go/zelda" class="item_node">塞尔达传说</a>

##

项目面试

Spring框架

解决的问题

解决了数据初始化的问题,可以将代码写的很简洁

容器是怎么样的

初始化是怎么做的

数据是怎么真正初始化的

切面编程的思维用处

整体框架是怎么样的,为什么要这样分

Velocity

为什么使用

前后端分离,达到解耦的目的

data如何穿过来的,如何解析的,支持什么呢

MyBatis

xml配置多重条件如何解析,如何与系统结合

登录、注册

网站安全,密码加salt,邮件激活
登录注册通过拦截器实现,思想,底层如何实现
token实现

分布式统一登录,token共享

敏感词过滤

将敏感词构造前缀树,对待检测文本通过一次扫描找出其中存在的敏感词,降低了时间复杂度

Redis

Redis设计与实现

点赞点踩的功能

使用Redis的set,不需要记录时间

timeline功能

使用list实现优先队列

实现异步框架

每一步操作后面附带的操作可能比较多,比如点赞之后站内信之类的,为了把结果及时返回给用户,所以采用了异步框架,自己用Redis的队列实现了异步框架,当时还考虑了使用java的优先队列来实现

异步框架构成

消息的发射,消息的处理,事件的模型定义,EventHandler

排序算法

timeline

自己实现了timeline,为了提高效率自己实现了异步框架

优点

  • 异步的
  • 通过注解的方式实现自己注册事件applicationContext.getBeansOfType找出所有实现了EventHandler接口的Handler
  • 使用了Redis的list实现优先队列

爬虫

如何提高效率的

多线程

用了什么解析数据

爬虫

问答网